package com.hyou.starter.autoconfigure.transaction;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;

/**
 * 在Spring boot环境下，避免对"*Service"的Bean拦截产生与Spring boot的命名冲突。
 * 
 * @author FengChangshuo
 * @version 1.0.0 2018-03-22 10:26:37 初始创建
 * @see org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator
 */
public class MyBeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
    
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 3431018088849100353L;
    
    private static final Logger log = LoggerFactory.getLogger(MyBeanNameAutoProxyCreator.class);
    
    /**
     * 限定的bean名称的配置
     */
    private List<String> beanNames;
    
    /**
     * 限定的包名称的配置
     */
    private List<String> packageNames;
    
    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
            TargetSource customTargetSource) {
        
        if (CollectionUtils.isEmpty(this.beanNames)) {
            return DO_NOT_PROXY;
        }
        
        for (String mappedName : this.beanNames) {
            if (FactoryBean.class.isAssignableFrom(beanClass)) {
                if (!mappedName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
                    continue;
                }
                mappedName = mappedName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
            }
            
            // 有些代理方式动态生成的Bean，这个值可能为空。这种的不处理。
            if (beanClass.getPackage() == null) {
                return DO_NOT_PROXY;
            }
            
            if (isMatch(beanName, mappedName, beanClass.getPackage().getName())) {
                return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
            }
            
            if (isBeanFactoryMatch(beanName, mappedName, beanClass.getPackage().getName())) {
                return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
            }
            
        }
        
        return DO_NOT_PROXY;
    }
    
    private boolean isMatch(String beanName, String mappedBeanName, String packageName) {
        boolean nameMatched = PatternMatchUtils.simpleMatch(mappedBeanName, beanName);
        
        if (!nameMatched) {
            return false;
        }
        
        if (this.packageNames == null) {
            return false;
        }
        
        for (String mappedPackageName : this.packageNames) {
            if (StringUtils.startsWithIgnoreCase(packageName, mappedPackageName)) {
                log.debug("beanClass package : {} ; beanName : {}", packageName, beanName);
                return true;
            }
        }
        
        return false;
    }
    
    private boolean isBeanFactoryMatch(String beanName, String mappedBeanName, String packageName) {
        BeanFactory beanFactory = getBeanFactory();
        
        if (beanFactory != null) {
            String[] aliases = beanFactory.getAliases(beanName);
            for (String alias : aliases) {
                if (isMatch(alias, mappedBeanName, packageName)) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    public void setBeanNames(String... beanNames) {
        Assert.notEmpty(beanNames, "'beanNames' must not be empty");
        this.beanNames = new ArrayList<>(beanNames.length);
        for (String mappedName : beanNames) {
            this.beanNames.add(StringUtils.trimWhitespace(mappedName));
        }
    }
    
    public void setPackageNames(String... packageNames) {
        Assert.notEmpty(packageNames, "'packageNames' must not be empty");
        this.packageNames = new ArrayList<>(packageNames.length);
        for (String mappedName : packageNames) {
            this.packageNames.add(StringUtils.trimWhitespace(mappedName));
        }
    }
    
}
